﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Messaging;
using System.Text;

using Common.Eventing;


namespace Common.Eventing.Msmq
{
    public class MsmqEventBroker : IEventBroker, IDisposable
    {
        private const string _noServiceMessage =
            "The MSMQ Service queue does not exist.  This is typically because the service is not running.  " +
            "Please start the service before starting clients.";

        private Dictionary<Type, List<IEventHandler>> _subscriptions;
        private Dictionary<IEventHandler, Guid> _localToRemoteLookup;
        private object _subscriptionLock;
        private MessageQueue _msmqServiceQueue;
        private MessageQueue _myQueue;


        public IEnumerable<IEventHandler> Subscriptions
        {
            get { return _subscriptions.Values.Aggregate((e1, e2) => e1.Union(e2).ToList()); }
        }


        public MsmqEventBroker(string msmqServiceQueue, string myQueue = null)
        {
            if (!MessageQueue.Exists(msmqServiceQueue))
            {
                throw new MsmqServiceNotAvailableException(_noServiceMessage);
            }

            if (string.IsNullOrEmpty(myQueue))
            {
                do
                {
                    myQueue = Path.GetRandomFileName();
                }
                while (MessageQueue.Exists(myQueue));
            }

            _myQueue = new MessageQueue(myQueue);
            _msmqServiceQueue = new MessageQueue(msmqServiceQueue);

            _localToRemoteLookup = new Dictionary<IEventHandler, Guid>();
            _subscriptions = new Dictionary<Type, List<IEventHandler>>();
            _subscriptionLock = new object();
        }


        public IEnumerable<IEventHandler> GetSubscriptionsFor(Type eventType)
        {
            IEnumerable<IEventHandler> subscriptions = null;

            lock (_subscriptionLock)
            {
                if (_subscriptions.ContainsKey(eventType))
                {
                    subscriptions = _subscriptions[eventType];
                }
                else
                {
                    subscriptions = new List<IEventHandler>();
                }
            }

            return subscriptions;
        }

        public void Subscribe(IEventHandler handler)
        {
            MsmqSubscription remoteHandler = new MsmqSubscription(handler.HandledEventType, _myQueue.Path);
            _msmqServiceQueue.Send(remoteHandler);

            lock (_subscriptionLock)
            {
                if (!_subscriptions.ContainsKey(handler.HandledEventType))
                {
                    _subscriptions.Add(handler.HandledEventType, new List<IEventHandler>());
                }

                _subscriptions[handler.HandledEventType].Add(handler);
                _localToRemoteLookup.Add(handler, remoteHandler.Id);
            }
        }

        public void Unsubscribe(IEventHandler handler)
        {
            Guid remoteHandlerId = Guid.Empty;

            lock (_subscriptionLock)
            {
                if (_subscriptions.ContainsKey(handler.HandledEventType))
                {
                    _subscriptions[handler.HandledEventType].Remove(handler);
                    remoteHandlerId = _localToRemoteLookup[handler];
                    _localToRemoteLookup.Remove(handler);
                }
            }

            if (remoteHandlerId != Guid.Empty)
            {
                _msmqServiceQueue.Send(remoteHandlerId);
            }
        }

        public void Publish(object eventMessage)
        {
            _msmqServiceQueue.Send(new MsmqEvent(eventMessage));
        }

        public void Dispose()
        {
            try { _msmqServiceQueue.Dispose(); }
            catch { }

            try
            {
                MessageQueue.Delete(_myQueue.Path);
                _myQueue.Dispose();
            }
            catch { }
        }
    }
}
